Step by Step API Load Testing with Gatling

Joe • updated : August 14, 2020

Overview

Performance testing is an essential part of modern software development. It helps teams to develop and deploy software applications with confidence in production environment. Performance testing implies simulating demand on a software application and then measuring how the application performs (response time and other metrics) in varying load conditions.

There are several open source load testing tools including Apache JMeter but, in this post, we will be focusing on Gatling.

Gatling is a powerful load testing solution, written in Scala and built upon Netty (for non-blocking HTTP) and Akka (for virtual user(s) orchestration).

Gatling supports HTTP, JMS, WebSocket, Server-Sent-Events and can also be used to test JDBC connections. It integrates with most development pipelines (Maven, Gradle, Jenkins etc.) and also includes colourful HTML reports.

Usage

In this tutorial, we will be testing a few endpoints that searches a life sciences index for relevant biological data. The search endpoints used in this tutorial were built on top the EBI Search REST API.

Using Spring Webclient and Okhttp, we developed blocking and nonblocking methods to search the EBI Search API Auto complete endpoint.

Here are the endpoints in our api that will be tested in this tutorial.

    @PostMapping("/service/webclient/auto/search")
    public Mono<Autocomplete> autocompleteSearch(@RequestParam(value = "query", required = true) String query) {
        if (query != null && query.length() >= 3) {
            return webclientSearch(query.toLowerCase());

        }
        return Mono.empty();
    }
    @GetMapping("/service/webclient/nonblocking/search")
    public Flux<Suggestion> queryServiceSearch(@RequestParam(value = "query", required = true) String query) {
        return suggestions(query.toLowerCase());
    }
    @GetMapping("/service/okhttp/search")
    public Optional<Autocomplete> okHttpSearch(@RequestParam(value = "query", required = true) String query) {
        return okHttp(query.toLowerCase());
    }
    @GetMapping("/service/blocking/search")
    public List<Suggestion> blockingSearch(@RequestParam(value = "query", required = true) String query) {
        return indexService.getSuggestions(query.trim());
    }

To test these endpoints, we create a Java maven project with the following prerequisites;

  • Java 1.8 or above
  • Maven (this example uses 3.6.0)
  • Gatling dependencies (gatling-charts-highcharts & maven plugin)
  • IntelliJ or any IDE with Scala Plugin (scala-sdk-2.13.2)

Step 1. Maven plugin

    <dependencies>
        <dependency>
            <groupId>io.gatling.highcharts</groupId>
            <artifactId>gatling-charts-highcharts</artifactId>
            <version>3.3.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <plugins>
        <plugin>
            <groupId>io.gatling</groupId>
            <artifactId>gatling-maven-plugin</artifactId>
            <version>3.0.5</version>
        </plugin>
    </plugins>

Step 2. Simulation setup

package com.computingfacts.simulations

import io.gatling.core.Predef._ // required for Gatling core structure DSL
import io.gatling.http.Predef._ // required for Gatling HTTP DSL
import scala.concurrent.duration._  // used for specifying duration unit, eg "1 second"
import scala.language.postfixOps

class Search extends Simulation {

We create a Scala class that extends Simulation with the required DSL (Domain specific language) imports.

Step 3. HTTP Protocol Configuration and Headers definition

Essential configuration includes the baseUrl and common headers. Other headers can be added in individual requests (e.g. GET, POST).

  val httpProtocol = http
    .baseUrl("http://localhost:9090/api")
    .disableCaching
    .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
    .doNotTrackHeader("1")
    .acceptLanguageHeader("en-UK,en;q=0.5")
    .acceptEncodingHeader("gzip, deflate")
    .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")

Step 4.  Scenario Definition

The scenario structure comprises of chaining two methods: exec and pause and we can use Scala DSL to define them. The exec describes the users’ behaviour or action, usually a request sent to the application that’s being tested. The Pause is used to simulate the think time of the user between consecutive requests.

  val searchScenario = scenario("Search")
    .exec(BlockingWebClientSearch.search,WebClientSearch.search,OkHttpSearch.search)

We divided our scenario into 3 reusable business processes ( WebClientSearch,OkHttpSearch and BlockingWebClientSearch ) so that we are able to easily reuse some parts and build complex behaviours without sacrificing maintenance. Each Object captures the user’s action (POST, GET) and interaction with the tested application endpoints.

  object WebClientSearch {
    val feeder = csv("data/search.csv").random
    val search =   feed(feeder).exec(
        http("Nonblocking WebClient Search")
          .get("/service/webclient/search")
          .queryParam("dataType","json")
          .queryParam("query","${Name}")
          .check(status.is(200))
      ).pause(5 seconds)
      .exec(
        http("Nonblocking Autocomplete Search (POST)")
          .post("/service/webclient/auto/search")
          .formParam("query","pyruva")
          .check(status.is(200))
          .check(jsonPath("$..suggestion").is("pyruvate"))
    ).pause(5 seconds)
  .feed(feeder).exec(
    http("Nonblocking WebClient Search - returns Flux ")
      .get("/service/webclient/nonblocking/search")
      .queryParam("dataType","json")
      .queryParam("query","${Name}")
      .check(status.is(200)))
  }

Step 5 Simulation definitions

This where we define the load, we want to inject into our API server over a period of time.

  setUp(
    searchScenario.inject(
      nothingFor(2 seconds),
      atOnceUsers(1),
      rampUsersPerSec(100) to(500) during(10 seconds)
    ).protocols(httpProtocol)
  ).maxDuration(1 minute)
    .assertions(global.responseTime.max.lte(60000))  //assert that the max response time of all requests is less than 60_000 ms
    .assertions(global.responseTime.percentile(75).lte(40000))
    .assertions(global.successfulRequests.percent.gte(80))
    .assertions(forAll.failedRequests.percent.lte(50))   //assert that every request has no more than 50% of failing requests
    .assertions(global.failedRequests.count.lte(50))
  • We configure httpProtocol on the setUp so that we pass the base URL and the common headers.
  • We inject a user and further 100 to 500 users per second for a period of 10 seconds.
  • We used the Assertins API to verify that global statistics such as response time or number of failed requests matches our expectations for the API simulation.

Step 6. Running the Simulation

Before we run our simulation, we need to configure some options (simulationsFolder, resourcesFolder and resultsFolder) in our gatlin-maven-plugin.

            <plugin>
                <groupId>io.gatling</groupId>
                <artifactId>gatling-maven-plugin</artifactId>
                <version>3.0.5</version>
                <configuration>
                    <runMultipleSimulations>true</runMultipleSimulations>                  
                    <simulationsFolder>${project.basedir}/src/test/java/com/computingfacts/simulations</simulationsFolder> 
                    <resourcesFolder>${project.basedir}/src/test/resources</resourcesFolder> 
                    <resultsFolder>${project.basedir}/target/gatling</resultsFolder>        
                </configuration>
            </plugin> 

 

Then run with maven mvn gatling:test but if we have multiple Simulations, then we need to enable the runMultipleSimulations option and then run as below;

mvn gatling:test -Dgatling.simulationClass=com.computingfacts.simulations.Search

Step 7. Test Reports

     Global information (Request count & Response time) and Response time distribution. 

To view the generated report, load the generated html in a browser

/Users/joseph/Projects/api-load-testing/target/gatling/search-20200209092247615/index.html

 

The example source code is available on GitHub.

Reference:

Gatling website 

 

Similar Posts ..

Subscribe to our monthly newsletter. No spam, we promise !

Guest